﻿using System;
using System.Windows;
using System.Windows.Input;
using Windows7.Multitouch.Manipulation;
using Windows7.Multitouch.WPF;

namespace ICodeShare.UI.Interactivity.Common
{
    public class Vector
    {
        #region Constructors

        public Vector()
        {
            X = 0;
            Y = 0;
        }
        public Vector(double x, double y)
        {
            X = x;
            Y = y;
        }

        #endregion Constructors

        #region Properties

        public double Length
        {
            get
            {
                return Math.Sqrt(LengthSquared);
            }
        }

        public double LengthSquared
        {
            get
            {
                return (this.X * this.X) + (this.Y * this.Y);
            }
        }

        public double X
        {
            get;
            set;
        }

        public double Y
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        public static explicit operator Size(Vector vector)
        {
            return new Size(Math.Abs(vector.X), Math.Abs(vector.Y));
        }

        public static explicit operator Point(Vector vector)
        {
            return new Point(vector.X, vector.Y);
        }

        public static bool operator !=(Vector vector1, Vector vector2)
        {
            return !(vector1 == vector2);
        }

        public static Vector operator *(Vector vector, double scalar)
        {
            return new Vector(vector.X * scalar, vector.Y * scalar);
        }

        public static Vector operator *(double scalar, Vector vector)
        {
            return new Vector(vector.X * scalar, vector.Y * scalar);
        }

        public static double operator *(Vector vector1, Vector vector2)
        {
            return ((vector1.X * vector2.X) + (vector1.Y * vector2.Y));
        }

        public static Vector operator +(Vector vector1, Vector vector2)
        {
            return new Vector(vector1.X + vector2.X, vector1.Y + vector2.Y);
        }

        public static Point operator +(Vector vector, Point point)
        {
            return new Point(point.X + vector.X, point.Y + vector.Y);
        }

        public static Vector operator -(Vector vector)
        {
            return new Vector(-vector.X, -vector.Y);
        }

        public static Vector operator -(Vector vector1, Vector vector2)
        {
            return new Vector(vector1.X - vector2.X, vector1.Y - vector2.Y);
        }

        public static Vector operator /(Vector vector, double scalar)
        {
            return (Vector)(vector * (1.0 / scalar));
        }

        public static bool operator ==(Vector vector1, Vector vector2)
        {
            return ((vector1.X == vector2.X) && (vector1.Y == vector2.Y));
        }

        public static Vector Add(Vector vector1, Vector vector2)
        {
            return new Vector(vector1.X + vector2.X, vector1.Y + vector2.Y);
        }

        public static Point Add(Vector vector, Point point)
        {
            return new Point(point.X + vector.X, point.Y + vector.Y);
        }

        public static double AngleBetween(Vector vector1, Vector vector2)
        {
            double y = (vector1.X * vector2.Y) - (vector2.X * vector1.Y);
            double x = (vector1.X * vector2.X) + (vector1.Y * vector2.Y);
            return (Math.Atan2(y, x) * 57.295779513082323);
        }

        public static double CrossProduct(Vector vector1, Vector vector2)
        {
            return ((vector1.X * vector2.Y) - (vector1.Y * vector2.X));
        }

        public static double Determinant(Vector vector1, Vector vector2)
        {
            return ((vector1.X * vector2.Y) - (vector1.Y * vector2.X));
        }

        public static Vector Divide(Vector vector, double scalar)
        {
            return (Vector)(vector * (1.0 / scalar));
        }

        public static bool Equals(Vector vector1, Vector vector2)
        {
            return (vector1.X.Equals(vector2.X) && vector1.Y.Equals(vector2.Y));
        }

        public static Vector Multiply(Vector vector, double scalar)
        {
            return new Vector(vector.X * scalar, vector.Y * scalar);
        }

        public static Vector Multiply(double scalar, Vector vector)
        {
            return new Vector(vector.X * scalar, vector.Y * scalar);
        }

        public static double Multiply(Vector vector1, Vector vector2)
        {
            return ((vector1.X * vector2.X) + (vector1.Y * vector2.Y));
        }

        public static Vector Subtract(Vector vector1, Vector vector2)
        {
            return new Vector(vector1.X - vector2.X, vector1.Y - vector2.Y);
        }
        public static Vector Subtruct(Point point1, Point point2)
        {
            return new Vector(point1.X - point2.X, point1.Y - point2.Y);
        }

        public override bool Equals(object o)
        {
            if ((o == null) || !(o is Vector))
            {
                return false;
            }
            Vector vector = (Vector)o;
            return Equals(this, vector);
        }

        public bool Equals(Vector value)
        {
            return Equals(this, value);
        }

        public override int GetHashCode()
        {
            return (this.X.GetHashCode() ^ this.Y.GetHashCode());
        }

        public void Negate()
        {
            this.X = -this.X;
            this.Y = -this.Y;
        }

        public void Rotate(double radians)
        {
            double sin = Math.Sin(radians);
            double cos = Math.Cos(radians);
            double xPrime = (X * cos) - (Y * sin);
            double yPrime = (X * sin) + (Y * cos);
            this.X = xPrime;
            this.Y = yPrime;
        }
        #endregion Methods
    }

    public class InertialManager
    {
        public VectorF InitialVelocity { get; set; }
        public float InitialAngularVelocity { get; set; }
        public float InitialExpansionVelocity { get; set; }
        public System.Diagnostics.Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();
        public void Reset()
        {
            InitialVelocity = new VectorF(0, 0);
            InitialAngularVelocity = 0;
            InitialExpansionVelocity = 0;
            _stopwatch.Reset();
            _stopwatch.Start();
        }

        public void Stop()
        {
            _stopwatch.Stop();
        }

        //update velocities, velocity = distance/time
        public void Update(Windows7.Multitouch.Manipulation.ManipulationDeltaEventArgs e, float history)
        {
            float elappsedMS = (float)_stopwatch.ElapsedMilliseconds;
            if (elappsedMS == 0)
                elappsedMS = 1;

            InitialVelocity = InitialVelocity * history + ((VectorF)e.TranslationDelta * (1F - history)) / elappsedMS;
            InitialAngularVelocity = InitialAngularVelocity * history + (e.RotationDelta * (1F - history)) / elappsedMS;
            InitialExpansionVelocity = InitialExpansionVelocity * history + (e.ExpansionDelta * (1F - history)) / elappsedMS;
            //Debug.WriteLine("1 ms:" + elappsedMS + " Velocity:" + InitialVelocity.Magnitude/elappsedMS + " Magnitude:" + InitialVelocity.Magnitude + " Angle:" + InitialAngularVelocity + " Expansion:" + InitialExpansionVelocity); 
            if (InitialVelocity.Magnitude / elappsedMS > .2F)
            {
                InitialVelocity = InitialVelocity * (.2F / InitialVelocity.Magnitude / elappsedMS);
                //Debug.WriteLine("2 ms:" + elappsedMS + " Velocity:" + InitialVelocity.Magnitude / elappsedMS + " Magnitude:" + InitialVelocity.Magnitude + " Angle:" + InitialAngularVelocity + " Expansion:" + InitialExpansionVelocity);

            }
            _stopwatch.Reset();
            _stopwatch.Start();
        }
    }

    /// <summary>
    /// Managing the manipulation of multiple pictures in the same time
    /// </summary>
    class ContextualElementTracker
    {
        //Map between touch ids and picture trackers
        TranslateRotateScaleBehavior moveRotateScaleBehavior;
        private readonly InertialManager inertiaParam = new InertialManager();
        private readonly ManipulationInertiaProcessor manipulationInertiaProcessor = new ManipulationInertiaProcessor(ProcessorManipulations.ALL, Factory.CreateTimer());

        bool FingerFlag;
        bool MouseFlag;

        public ContextualElementTracker(TranslateRotateScaleBehavior moveRotateScaleBehavior, FrameworkElement element)
        {
            this.moveRotateScaleBehavior = moveRotateScaleBehavior;

            element.Loaded += (s, e) =>
            {
                Factory.EnableStylusEvents(Application.Current.MainWindow);
                element.StylusDown += ProcessDown;
                element.StylusUp += ProcessUp;
                element.StylusMove += ProcessMove;
                element.StylusLeave += ProcessLeave;
                element.MouseDown += ProcessDown;
                element.MouseUp += ProcessUp;
                element.MouseLeave += ProcessLeave;
                element.MouseMove += ProcessMove;

            };
            manipulationInertiaProcessor.ManipulationStarted += ProcessManipulationStarted;
            manipulationInertiaProcessor.ManipulationCompleted += ProcessManipulationCompleted;
            manipulationInertiaProcessor.ManipulationDelta += ProcessManipulationDelta;
            manipulationInertiaProcessor.BeforeInertia += OnBeforeInertia;
        }

        public event EventHandler<Windows7.Multitouch.Manipulation.ManipulationDeltaEventArgs> ManipulationCompleted = (s, e) => { };

        public void ProcessDown(object sender, StylusEventArgs args)
        {
            FingerFlag = true;
            moveRotateScaleBehavior.BringToFront();
            Point touchLocation = moveRotateScaleBehavior.GetPosition(args);
            manipulationInertiaProcessor.ProcessDown((uint)args.StylusDevice.Id, touchLocation.ToDrawingPointF());
        }

        public void ProcessUp(object sender, StylusEventArgs args)
        {
            FingerFlag = false;
            Point touchLocation = moveRotateScaleBehavior.GetPosition(args);
            manipulationInertiaProcessor.ProcessUp((uint)args.StylusDevice.Id, touchLocation.ToDrawingPointF());
        }

        public void ProcessMove(object sender, StylusEventArgs args)
        {
            Point touchLocation = moveRotateScaleBehavior.GetPosition(args);
            manipulationInertiaProcessor.ProcessMove((uint)args.StylusDevice.Id, touchLocation.ToDrawingPointF());
        }
        public void ProcessLeave(object sender, StylusEventArgs args)
        {
            FingerFlag = false;
            Point touchLocation = moveRotateScaleBehavior.GetPosition(args);
            manipulationInertiaProcessor.ProcessUp((uint)args.StylusDevice.Id, touchLocation.ToDrawingPointF());
        }
        public void ProcessDown(object sender, MouseEventArgs args)
        {
            if (!FingerFlag)
            {
                MouseFlag = true;
                moveRotateScaleBehavior.BringToFront();
                Point touchLocation = args.GetPosition(null);
                manipulationInertiaProcessor.ProcessDown(255, touchLocation.ToDrawingPointF());
            }
        }
        public void ProcessUp(object sender, MouseEventArgs args)
        {
            MouseFlag = false;
            if (!FingerFlag)
            {
                Point touchLocation = args.GetPosition(null);
                manipulationInertiaProcessor.ProcessUp(255, touchLocation.ToDrawingPointF());
            }
        }
        public void ProcessMove(object sender, MouseEventArgs args)
        {
            if (MouseFlag)
            {
                Point touchLocation = args.GetPosition(null);
                manipulationInertiaProcessor.ProcessMove(255, touchLocation.ToDrawingPointF());
            }
        }
        public void ProcessLeave(object sender, MouseEventArgs args)
        {
            MouseFlag = false;
            if (!FingerFlag)
            {
                Point touchLocation = args.GetPosition(null);
                manipulationInertiaProcessor.ProcessUp(255, touchLocation.ToDrawingPointF());
            }
        }



        private void ProcessManipulationStarted(object sender, Windows7.Multitouch.Manipulation.ManipulationStartedEventArgs e)
        {
            inertiaParam.Reset();
        }
        private void ProcessManipulationDelta(object sender, Windows7.Multitouch.Manipulation.ManipulationDeltaEventArgs e)
        {
            ManipulationCompleted(sender, e);
            inertiaParam.Update(e, 0.4F);
        }
        private void ProcessManipulationCompleted(object sender, Windows7.Multitouch.Manipulation.ManipulationCompletedEventArgs e)
        {
            inertiaParam.Stop();
        }

        //Fingers removed, start inertia
        void OnBeforeInertia(object sender, BeforeInertiaEventArgs e)
        {
            manipulationInertiaProcessor.InertiaProcessor.InertiaTimerInterval = 15;
            manipulationInertiaProcessor.InertiaProcessor.MaxInertiaSteps = 500;
            manipulationInertiaProcessor.InertiaProcessor.InitialVelocity = inertiaParam.InitialVelocity;
            manipulationInertiaProcessor.InertiaProcessor.DesiredDisplacement = inertiaParam.InitialVelocity.Magnitude * 250;
            manipulationInertiaProcessor.InertiaProcessor.InitialAngularVelocity = inertiaParam.InitialAngularVelocity * 20F / (float)Math.PI;
            manipulationInertiaProcessor.InertiaProcessor.DesiredRotation = Math.Abs(inertiaParam.InitialAngularVelocity * manipulationInertiaProcessor.InertiaProcessor.InertiaTimerInterval * 540F / (float)Math.PI);
            manipulationInertiaProcessor.InertiaProcessor.InitialExpansionVelocity = inertiaParam.InitialExpansionVelocity * 15;
            manipulationInertiaProcessor.InertiaProcessor.DesiredExpansion = Math.Abs(inertiaParam.InitialExpansionVelocity * 4F);
        }
    }
}
